﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace NMF.Optimizations.Linq
{
    /// <summary>
    /// This class represents the default implementation to fulfill the C# bind-operator (SelectMany). 
    /// </summary>
    /// <typeparam name="T1">The inner type of the outer monad instance</typeparam>
    /// <typeparam name="T2">The inner type of the inner monad instance</typeparam>
    /// <typeparam name="TTarget">The inner type of the target monad instance</typeparam>
    /// <remarks>Please see the documentation of the SelectMany extension method and its use with the LINQ-syntax for more details</remarks>
    internal class CompositeSource<T1, T2, TTarget> : SelectingSource<T1, T2, TTarget>, IObservableSource<TTarget>, ISourceObserver<T1>
    {
        public CompositeSource(IObservableSource<T1> source, Func<T1, IObservableSource<T2>> func, Func<T1, T2, TTarget> selector) : base(source, selector)
        {
            if (func == null) throw new ArgumentNullException("func");

            SubsourceSelector = func;

            foreach (var item in source)
            {
                var subSource = func(item);
                subSource.Subscribe(new ElementObserver(item, this));
            }
            source.Subscribe(this);
        }

        void ISourceObserver<T1>.OnDeletedItem(T1 item)
        {
            var subSource = SubsourceSelector(item);
            foreach (var element in subSource)
            {
                OnDeletedItem(SelectAndRemove(item, element));
            }
            subSource.Unsubscribe(observer =>
                {
                    var element = observer as ElementObserver;
                    if (element == null) {
                        return false;
                    }
                    else
                    {
                        return object.Equals(element.item, item) && element.parent == this;
                    }
                });
        }

        void ISourceObserver<T1>.OnNewItem(T1 item)
        {
            var subSource = SubsourceSelector(item);
            foreach (var element in subSource)
            {
                OnNewItem(Select(item, element));
            }
            subSource.Subscribe(new ElementObserver(item, this));
        }

        void ISourceObserver<T1>.OnUpdatedItem(T1 item)
        {
            var subSource = SubsourceSelector(item);
            foreach (var element in subSource)
            {
                var oldSelection = Select(item, element);
                var newSelection = Selector(item, element);

                if (object.Equals(oldSelection, newSelection))
                {
                    OnUpdatedItem(oldSelection);
                }
                else
                {
                    OnDeletedItem(oldSelection);
                    OnNewItem(newSelection);
                }
            }
        }

        void ISourceObserver<T1>.OnReset()
        {
            OnReset();
        }

        private class ElementObserver : ISourceObserver<T2>
        {
            public T1 item;
            public CompositeSource<T1, T2, TTarget> parent;

            public ElementObserver(T1 item, CompositeSource<T1, T2, TTarget> parent)
            {
                this.item = item;
                this.parent = parent;
            }

            public void OnNewItem(T2 element)
            {
                parent.OnNewItem(parent.Select(item, element));
            }

            public void OnUpdatedItem(T2 element)
            {
                parent.OnUpdatedItem(parent.Select(item, element));
            }

            public void OnDeletedItem(T2 element)
            {
                parent.OnDeletedItem(parent.SelectAndRemove(item, element));
            }

            public void OnReset()
            {
                parent.OnReset();
            }
        }

        private Func<T1, IObservableSource<T2>> SubsourceSelector { get; set; }

        public override IEnumerator<TTarget> GetEnumerator()
        {
            return (from item in (IEnumerable<T1>)Source
                    from element in (IEnumerable<T2>)(SubsourceSelector(item))
                    select Select(item, element)).GetEnumerator();
        }
    }
}
